
/* GCSx
** SLIDER.CPP
**
** Numerical slider widget
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

WSlider::WSlider(int sId, int sBegin, int sEnd, int* sSetting, int sLength) : Widget(sId, blankString, sSetting) { start_func
    assert(sBegin <= sEnd);
    assert(sSetting);
    assert((sLength > 0) || (sLength == -1));
    
    if (sLength == -1) pixelLength = GUI_SLIDER_TRACKLENGTH;
    else pixelLength = sLength;
    
    rangeBegin = sBegin;
    rangeEnd = sEnd;
    linkedTo = NULL;
    
    // Tab extends a certain amount past the end of the track
    resize(GUI_SLIDER_TABWIDTH + pixelLength, GUI_SLIDER_TABHEIGHT);

    load(); // Always keep a valid setting in it, due to linkTo
}

int WSlider::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);
    assert(parent);

    switch (event->type) {
        case SDL_INPUTFOCUS:
            if (event->user.code & 1) {
                if (!haveFocus) {
                    haveFocus = 1;
                    setDirty();
                }
            }
            else {
                if ((haveFocus) || (dragging)) {
                    dragging = haveFocus = 0;
                    setDirty();
                }
            }
            return 1;
            
        case SDL_KEYDOWN:
            switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
                case SDLK_LEFT:
                case SDLK_UP:
                    statePixel(pixelValue - 1);
                    return 1;
                    
                case SDLK_RIGHT:
                case SDLK_DOWN:
                    statePixel(pixelValue + 1);
                    return 1;

                case SDLK_PAGEUP:
                    statePixel(pixelValue - GUI_SLIDER_TABWIDTH);
                    return 1;

                case SDLK_PAGEDOWN:
                    statePixel(pixelValue + GUI_SLIDER_TABWIDTH);
                    return 1;

                case SDLK_HOME:
                    state(rangeBegin);
                    return 1;

                case SDLK_END:
                    state(rangeEnd);
                    return 1;
            }
            break;
            
        case SDL_MOUSEBUTTONUP:
            if ((event->button.button == SDL_BUTTON_LEFT) && (dragging)) {
                dragging = 0;
                setDirty();
            }
            return 1;
            
        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            if (event->button.button == SDL_BUTTON_LEFT) {
                if (event->button.x < pixelValue) {
                    statePixel(pixelValue - GUI_SLIDER_TABWIDTH);
                }
                else if (event->button.x >= pixelValue + GUI_SLIDER_TABWIDTH) {
                    statePixel(pixelValue + GUI_SLIDER_TABWIDTH);
                }
                else {
                    dragging = 1;
                    draggingX = event->button.x;
                    draggingValue = pixelValue;
                    setDirty();
                }
                return 1;
            }
            else if (event->button.button == SDL_BUTTON_WHEELUP) {
                statePixel(pixelValue - GUI_SLIDER_TABWIDTH);
                return 1;
            }
            else if (event->button.button == SDL_BUTTON_WHEELDOWN) {
                statePixel(pixelValue + GUI_SLIDER_TABWIDTH);
                return 1;
            }
            break;

        case SDL_MOUSEMOTION:
            if (dragging) {
                // Signed required in case negative
                statePixel(draggingValue + ((Sint16)event->motion.x) - draggingX);
            }
            return 1;
    }
    
    return 0;
}

void WSlider::linkTo(WNumberBox* linked) { start_func
    if (linked == NULL) {
        linkedTo = NULL;
    }
    else {
        linkedTo = linked;
        linkedTo->state(currentValue);
    }
}

void WSlider::siblingModified(Widget* modified) { start_func
    assert(modified);
    assert(modified != this);
    
    if (modified == linkedTo) stateFromLoad(linkedTo->state());
}

void WSlider::load() { start_func
    dragging = 0;
    stateFromLoad(*((int*)setting));
    setDirty();
}

void WSlider::apply() { start_func
    *((int*)setting) = state();
}

void WSlider::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw all
        if (dirty) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
        
            xOffset += x;
            yOffset += y;

            // This widget makes no attempt at partial redraw other than the initial fill.
            SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_FILL]);
            
            // The track is extended in total length by the tracksize, to make
            // the appearance balanced
            drawGuiBoxInvert(xOffset + GUI_SLIDER_TABWIDTH / 2 - GUI_SLIDER_TRACKSIZE / 2, yOffset + GUI_SLIDER_TABHEIGHT / 2 - GUI_SLIDER_TRACKSIZE / 2, pixelLength + GUI_SLIDER_TRACKSIZE, GUI_SLIDER_TRACKSIZE, 1, destSurface);
            
            // The tab is centered on it's pixel; 0 is actually one pixel off the tracksize,
            // lining up with the left center pixel of the tab (since the tab is an even
            // width) This creates a balanced appearance from 0 to pixelLength.
            // It is drawn inverted if currently depressed.
            if (dragging) {
                drawRect(xOffset + pixelValue, yOffset, GUI_SLIDER_TABWIDTH, GUI_SLIDER_TABHEIGHT, guiPacked[COLOR_FILL], destSurface);
                drawGuiBoxInvert(xOffset + pixelValue, yOffset, GUI_SLIDER_TABWIDTH, GUI_SLIDER_TABHEIGHT, 2, destSurface);
            }
            else {
                drawGuiBox(xOffset + pixelValue, yOffset, GUI_SLIDER_TABWIDTH, GUI_SLIDER_TABHEIGHT, 2, destSurface);
            }
            
            // Focus?
            if (haveFocus) drawFocusBox(xOffset, yOffset, width, height, destSurface);
            
            // This widget has no special disabled appearance.
        }
    }
}

int WSlider::state() const { start_func
    return currentValue;
}

int WSlider::state(int newValue) { start_func
    int markDirty = 0;
    if (newValue != currentValue) markDirty = 1;
    
    stateFromLoad(newValue);

    if ((markDirty) && (linkedTo)) linkedTo->state(newValue);
    
    // May not have a parent, as this is called during constructor
    if (parent) parent->childModified(this);

    return currentValue;
}

int WSlider::stateFromLoad(int newValue) { start_func
    if (newValue < rangeBegin) newValue = rangeBegin;
    else if (newValue > rangeEnd) newValue = rangeEnd;

    int markDirty = 0;
    if (newValue != currentValue) markDirty = 1;

    currentValue = newValue;
    
    if (rangeEnd == rangeBegin) {
        pixelValue = pixelLength;
    }
    else {
        // sBegin will always result in 0
        // sEnd will always result in pixelLength
        // values in between are equally spaced
        // *2, +1, /2 allows for rounding off
        pixelValue = ((newValue - rangeBegin) * pixelLength * 2 / (rangeEnd - rangeBegin) + 1) / 2;
    }
    
    if (markDirty) {
        setDirty();        
    }

    return currentValue;
}

int WSlider::statePixel(int newPixel) { start_func
    if (newPixel < 0) newPixel = 0;
    else if (newPixel > pixelLength) newPixel = pixelLength;
    
    if (newPixel != pixelValue) {
        // *2, +1, /2 allows for rounding off
        return state(rangeBegin + (newPixel * (rangeEnd - rangeBegin) * 2 / pixelLength + 1) / 2);
    }

    return state();
}


